<?php
require_once "Logger.class.php";
/**
 * HttpClient
 *
 */
class HttpClient
{
	//是否强制使用socket_http
	private $_use_socket_http = false;

	// HTTP 请求构造
	private $_request_url;
	private $_request_params;
	private $_request_method;
	private $_request_multi;
	private $_response_http_code;
	private $_parsed_url;

    private $_useragent='';
	private $_response_header='';
	private $_response_body='';
	private $_request_header=array("Connection: Close");

    public $connecttimeout = 30;
    public $timeout = 30;
    public $ssl_verifypeer = false;

    public function  __construct($url , $params=array() , $method='GET' , $multi=false) {
		$this->_request_url = $url;
		$this->_request_params = $params;
		$this->_request_method = $method;
		$this->_request_multi = $multi;
    }

	public function setUseSoketHttp($socket_http) {
		$this->_use_socket_http = $socket_http;
	}

	public function setUserAgent($ua) {
		$this->_useragent = $ua;
	}

	public function addRequestHeader($header) {
		if (is_array($header)) {
			foreach($header as $header_key => $header_val) {
		        $this->_request_header[]=$header_key.": ".$header_val;
			}
		} else {
		    $this->_request_header[]=$header;
		}
		return $this->_request_header;
	}

	public function getResponseHttpCode($as_array=false) {
		return $this->_response_http_code;
	}

	public function getResponseContentType() {
		$header_arr = $this->getResponseHeader(true);
		if (!array_key_exists("Content-Type",$header_arr)) {
			return "";
		}
		return $header_arr["Content-Type"];
	}

	public function getRequestUrl() {
		return $this->_request_url;
	}

	public function getParsedUrl() {
		return $this->_parsed_url;
	}

	public function getResponseHeader($as_array=false) {
		if ($as_array) {
			if (!empty($this->_response_header)) {
		    	foreach(explode("\r\n",$this->_response_header) as $header_single) {
					$pos = strpos($header_single,":");
					if ($pos) {
		    	    	$header_key = trim(substr($header_single,0,$pos));
		    	    	$header_val = trim(substr($header_single,$pos+1));
						if ($header_key == "Set-Cookie") { // set-Cookie允许重复
							if (!array_key_exists("Set-Cookie", $header_arr)) {
								$header_arr[$header_key] = array();
							}
							$header_arr[$header_key][] = $header_val;
						} else {
		    		        $header_arr[$header_key] = $header_val;
						}
					} else {
		    		    $header_arr["Head"] = trim($header_single);
					}
		    	}
		    	return $header_arr;
			}
			return array();
		}
		return $this->_response_header;
	}

	public function getResponseBody() {
		return $this->_response_body;
	}

	public function detectEncoding() {

		$resonse_headers = $this->getResponseHeader($as_array=true);

		// header中未指定编码则从body中提取正确的编码
		if (array_key_exists("Content-Type",$resonse_headers) && strpos("charset",$resonse_headers["Content-Type"])) {
			$content_type = $resonse_headers["Content-Type"];
		} else {
			$content_type = $this->getResponseBody();
		} 

		// 匹配 <meta charset="xxx">
		// 或CSS中 @charset "utf-8"
		if(!empty($content_type) && preg_match("@charset\s*=?\s*[\"\']?\s*([^\"^']*)@si",$content_type,$matches)) {
			$encoding = strtoupper($matches[1]);
			if($encoding == "GB2312" || $encoding == "GBK") {
				$encoding = "GB18030";
			}
		} else {
			$encoding = "";
		}
		return strtolower($encoding);
	}

	/**
	 * socket http debug
	 */
    private function fwrite($handle,$data)
    {
		// 成功写入socket的字节数
        $fwritted = @fwrite($handle, $data);
		if (!$fwritted) {
			Logger::warn("write socket data has failed, socket maybe forced reset");
		}
    }
    /**
     * Http请求接口
     * 优先使用curl，在不支持curl的情况下，使用socket操作
     *
     * 发送文件需要使用 $method = 'POST' ， 同时指定 $multi = array('{fieldname}'=>'/path/to/file)
     *
     * @param string $url
     * @param array $params
     * @param string $method 支持 GET / POST / DELETE
     * @param false|array $multi false:普通post array: array ( '{fieldname}'=>'/path/to/file' ) 文件上传
     * @return string
     */
    public function sendRequest()
    {
        if(!$this->_use_socket_http && function_exists('curl_init'))
        {
            $this->_response_body = $this->curl_http($this->_request_url, $this->_request_params, $this->_request_method, $this->_request_multi);
        }
        else
        {
            $this->_response_body = $this->socket_http($this->_request_url, $this->_request_params, $this->_request_method, $this->_request_multi);
        }
    }

    protected function curl_http( $url , $params , $method='GET' , $multi=false )
    {
		$method = trim(strtoupper($method));
        $urls = parse_url($url);
		if (!array_key_exists('path',$urls)) {
			$urls['path'] = '/';
		}
		if (!array_key_exists('query',$urls)) {
			$urls['query'] = '';
		}
		if (!array_key_exists('port',$urls)) {
			$urls['port'] = '80';
		}
		
		$this->_parsed_url = $urls;
        $ci = curl_init();

        curl_setopt($ci, CURLOPT_USERAGENT, $this->_useragent);
        curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
        curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
        curl_setopt($ci, CURLOPT_HEADER, true);
		curl_setopt($ci, CURLOPT_REFERER, $urls['scheme'] . "://" . $urls['host'] . $urls['path']);
		if (!empty($this->_request_header)) {
		    curl_setopt($ci, CURLOPT_HTTPHEADER, $this->_request_header);
		}

        switch ($method)
        {
            case 'POST':
                curl_setopt($ci, CURLOPT_POST, TRUE);
                if (!empty($params))
                {
                    if($multi)
                    {
                        foreach($multi as $key => $file)
                        {
                            $params[$key] = '@' . $file;
                        }
                        curl_setopt($ci, CURLOPT_POSTFIELDS, $params);
                        curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect: ') );
                    }
                    else
                    {
                        curl_setopt($ci, CURLOPT_POSTFIELDS, http_build_query($params));
                    }
                }
                break;
            case 'DELETE':
            case 'GET':
                $method == 'DELETE' && curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
                if (!empty($params))
                {
                    $url = $url . (strpos($url, '?') ? '&' : '?')
                        . (is_array($params) ? http_build_query($params) : $params);
                }
                break;
        }
        curl_setopt($ci, CURLINFO_HEADER_OUT, TRUE );
        curl_setopt($ci, CURLOPT_URL, $url);

        $response = curl_exec($ci);
        $this->_response_http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
	    $response_info = curl_getinfo($ci);		
        $header_size= $response_info["header_size"];
		$this->_response_header = trim(substr($response, 0 , $header_size));
        $response_body = substr($response, $header_size);

		if (!array_key_exists("request_header",$response_info)) {
            $response_info["request_header"] = "";
		}
		Logger::info($method." ".$url);
		Logger::debug("request with curl "."yes");
		Logger::debug("request url ".$url);
		Logger::debug("request method ".$method);
		Logger::debug("request header ".$response_info["request_header"]);
		if ($method=="POST") {
		    Logger::debug("post body(\$multipart not included) ".http_build_query($params));
		}
		Logger::debug("remote server response ".$response);
        curl_close ($ci);
        return $response_body;
    }
    /**
     * Http请求接口
     *
     * @param string $url
     * @param array $params
     * @param string $method 支持 GET / POST / DELETE
     * @param false|array $multi false:普通post array: array ( '{fieldname}'=>'/path/to/file' ) 文件上传
     * @return string
     */
    protected function socket_http( $url , $params , $method='GET' , $multi=false )
    {
		$method = trim(strtoupper($method));
        $postdata = '';
        $urls = parse_url($url);
		if (!array_key_exists('path',$urls)) {
			$urls['path'] = '/';
		}
		if (!array_key_exists('query',$urls)) {
			$urls['query'] = '';
		}
		if (!array_key_exists('port',$urls)) {
			$urls['port'] = '80';
		}
		$this->_parsed_url = $urls;
        $httpurl = $urlpath = $urls['path'] . ($urls['query'] ? '?' . $urls['query'] : '');
        if( !$multi )
        {
            $parts = array();
            foreach ($params as $key => $val)
            {
                $parts[] = urlencode($key) . '=' . urlencode($val);
            }
            if ($parts)
            {
                $postdata = implode('&', $parts);
                $httpurl = $httpurl . (strpos($httpurl, '?') ? '&' : '?') . $postdata;
            }
            else
            {
            }
        }

        $host = $urls['host'];
        $port = $urls['port'] ? $urls['port'] : 80;
        $version = '1.1';
        if($urls['scheme'] === 'https')
        {
            $port = 443;
        }
        $headers = array();
        if($method == 'GET')
        {
            $headers[] = "GET $httpurl HTTP/$version";
        }
        else if($method == 'DELETE')
        {
            $headers[] = "DELETE $httpurl HTTP/$version";
        }
        else
        {
            $headers[] = "POST $urlpath HTTP/$version";
        }
        $headers[] = 'User-Agent: '.$this->_useragent;
        $headers[] = 'Host: ' . $host;
        $headers[] = 'Referer: ' . $urls['scheme'] . "://" . $host . $urls['path'];
		if(!empty($this->_request_header)) {
			foreach($this->_request_header as $single_header) {
                $headers[] = $single_header;
			}
		}

        if($method == 'POST')
        {
            if($multi)
            {
                $boundary = uniqid('------------------');
                $MPboundary = '--' . $boundary;
                $endMPboundary = $MPboundary . '--';
                $multipartbody = '';
                $headers[]= 'Content-Type: multipart/form-data; boundary=' . $boundary;
                foreach($params as $key => $val)
                {
                    $multipartbody .= $MPboundary . "\r\n";
                    $multipartbody .= 'Content-Disposition: form-data; name="' . $key . "\"\r\n\r\n";
                    $multipartbody .= $val . "\r\n";
                }
                foreach($multi as $key => $path)
                {
                    $multipartbody .= $MPboundary . "\r\n";
                    $multipartbody .= 'Content-Disposition: form-data; name="' . $key . '"; filename="' . pathinfo($path, PATHINFO_BASENAME) . '"' . "\r\n";
                    $multipartbody .= 'Content-Type: ' . self::get_image_mime($path) . "\r\n\r\n";
                    $multipartbody .= file_get_contents($path) . "\r\n";
                }
                $multipartbody .= $endMPboundary . "\r\n";
                $postdata = $multipartbody;
            }
            else
            {
                $headers[]= 'Content-Type: application/x-www-form-urlencoded';
				if (!empty($params)) {
				    $postdata = http_build_query($params);
				}
            }
        }
        $ret = '';
        $fp = fsockopen($host, $port, $errno, $errstr, 5);

		Logger::info($method." ".$url);
		Logger::debug("reuqest with sockethttp "."yes");
		Logger::debug("reuqest url ".$url);
		Logger::debug("reuqest method ".$method);

        if(! $fp)
        {
            $error = 'Open Socket Error';
		    Logger::warn($error);
            return '';
        }
        else
        {
            if( $method != 'GET' && $postdata )
            {
                $headers[] = 'Content-Length: ' . strlen($postdata);
            }
			$headers_to_send = implode("\r\n", $headers);
			Logger::debug("reuqest header ".$headers_to_send);
            $this->fwrite($fp, $headers_to_send);
            $this->fwrite($fp, "\r\n\r\n");
            if( $method != 'GET' && $postdata )
            {
				if (!empty($params)) {
				    Logger::debug("post body(\$multipart not included) ".http_build_query($params));
				}
                $this->fwrite($fp, $postdata);
            }
            //skip headers
            while(! feof($fp))
            {
                $ret .= fgets($fp, 1024);
            }
			Logger::debug("remote server response ".$ret);
            fclose($fp);
            $pos = strpos($ret, "\r\n\r\n");
            if($pos)
            {
                $rt = trim(substr($ret , $pos+1));
				$responseHead = trim(substr($ret, 0 , $pos));
				$responseHeads = explode("\r\n", $responseHead);
				$httpcode = explode(' ', $responseHeads[0]);
				$this->_response_http_code = $httpcode[1];
		        $this->_response_header = $responseHead;
                if(strpos( substr($ret , 0 , $pos), 'Transfer-Encoding: chunked'))
                {
                    $response = explode("\r\n", $rt);
                    $t = array_slice($response, 1, - 1);

                    return implode('', $t);
                }
                return $rt;
            }
            return '';
        }
    }
}
